﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;

namespace LH.Comunicacion
{
 
    public class Cliente
    {
        #region --- Variables y Propiedades ---
        private IAsyncResult m_result;
        private AsyncCallback m_pfnCallBack;
        private Socket m_clientSocket;

        private string dataRecibida = string.Empty;
        private string cadenaTerminador = ";";
        #endregion

        #region --- Delegatos y Eventos ---
        /// <summary>
        /// Delegado: Firma del delegado de Sucesos en el cliente
        /// </summary>
        /// <param name="mensaje">Mensaje o suceso</param>
        public delegate void sucesosClienteHandler(string mensaje);

        /// <summary>
        /// Evento: Que sera ejecutado al ocurrir sucesos en el cliente
        /// </summary>
        public event sucesosClienteHandler sucesosCliente;

        /// <summary>
        /// Delegado: Firma del delegado de Recepcion de Data
        /// </summary>
        /// <param name="mensaje">Data recibida</param>
        public delegate void recepcionDataHandler(string mensaje);

        /// <summary>
        /// Evento: Que sera ejecutado al ocurrir recepcion de data en el cliente
        /// </summary>
        public event recepcionDataHandler recepcionData;

        /// <summary>
        /// Delegado: Firma del delegado de Termino de Conexion con el servidor
        /// </summary>
        /// <param name="mensaje">Mensaje de desconexion</param>
        public delegate void terminoConexionHanler(string mensaje);

        /// <summary>
        /// Evento: Que sera ejecutado al ocurrir termino de conexion con el servidor
        /// </summary>
        public event terminoConexionHanler terminoConexion;

        private void OnSucesosCliente(string mensaje)
        {
            if (sucesosCliente != null)
            {
                sucesosCliente(mensaje);
            }
        }

        private void OnRecepcionData(string mensaje)
        {
            if (recepcionData != null)
            {
                recepcionData(mensaje);
            }
        }

        private void OnTerminoConexion(string mensaje)
        {
            if (terminoConexion != null)
            {
                terminoConexion(mensaje);
            }
        }
        #endregion

        #region --- Metodos Propios ---
        /// <summary>
        /// Metodo: Permite empezar la conexion con el servidor
        /// </summary>
        /// <param name="ipAdrress">IP del servidor</param>
        /// <param name="puerto">Puerto escucha del servidor</param>
        /// <returns>True o False</returns>
        public bool iniciar(string ipAdrress, int puerto)
        {
            bool resultado = false;
            try
            {
                // Create the socket instance
                m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                // Cet the remote IP address
                IPAddress ip = IPAddress.Parse(ipAdrress);
                // Create the end point 
                IPEndPoint ipEnd = new IPEndPoint(ip, puerto);

                // Connect to the remote host
                m_clientSocket.Connect(ipEnd);
                if (m_clientSocket.Connected)
                {
                    resultado = true;
                    //Wait for data asynchronously 
                    WaitForData();
                }
            }
            catch (SocketException se)
            {
                OnSucesosCliente("Conexion finalizada, el servidor esta operativo? : " + se.Message);
            }

            return resultado;
        }

        /// <summary>
        /// Metodo: Permite finalizar la conexion con el servidor
        /// </summary>
        /// <returns>True o False</returns>
        public bool detener()
        {
            bool resultado = false;
            if (m_clientSocket != null)
            {
                m_clientSocket.Close();
                m_clientSocket = null;

                resultado = true;
            }

            return resultado;
        }

        /// <summary>
        /// Metodo: Permite enviar mensaje al servidor
        /// </summary>
        /// <param name="mensaje">Mensaje a enviar</param>
        public void enviarMensaje(string mensaje)
        {
            try
            {
                if (mensaje.Equals(string.Empty)) { return; }
                mensaje = agregarTerminador(mensaje);

                byte[] byData = System.Text.Encoding.ASCII.GetBytes(mensaje);
                if (m_clientSocket != null)
                {
                    m_clientSocket.Send(byData);
                }
            }
            catch (SocketException se)
            {
                OnSucesosCliente(se.Message);
            }	
        }

        private string agregarTerminador(string mensaje)
        {
            string finMensaje = string.Empty;
            string mensajeResultado = string.Empty;

            if (mensaje.Length > 0)
            {
                finMensaje = mensaje.Substring(mensaje.Length - 1, 1);

                if (!finMensaje.Equals(this.cadenaTerminador))
                {
                    mensajeResultado = mensaje + this.cadenaTerminador;
                }
            }

            return mensajeResultado;
        }
        #endregion

        #region --- Metodos del Cliente ---
        private void WaitForData()
        {
            try
            {
                if (m_clientSocket == null)
                {
                    return;
                }

                if (m_pfnCallBack == null)
                {
                    m_pfnCallBack = new AsyncCallback(OnDataReceived);
                }
                SocketPacket theSocPkt = new SocketPacket();
                theSocPkt.thisSocket = m_clientSocket;
                // Start listening to the data asynchronously
                m_result = m_clientSocket.BeginReceive(theSocPkt.dataBuffer,
                                                        0, theSocPkt.dataBuffer.Length,
                                                        SocketFlags.None,
                                                        m_pfnCallBack,
                                                        theSocPkt);
            }
            catch (SocketException se)
            {
                if ((se.SocketErrorCode == SocketError.Disconnecting) ||
                     (se.SocketErrorCode == SocketError.ConnectionReset))
                {
                    OnTerminoConexion("Se cerro la conexion con el servidor");//se.Message);
                }
                else
                {
                    OnSucesosCliente(se.Message);
                }
            }

        }

        private class SocketPacket
        {
            public System.Net.Sockets.Socket thisSocket;
            public byte[] dataBuffer = new byte[1];
        }

        private void OnDataReceived(IAsyncResult asyn)
        {
            try
            {
                SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
                int iRx = theSockId.thisSocket.EndReceive(asyn);
                char[] chars = new char[iRx + 1];
                System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
                System.String szData = new System.String(chars);

                if (szData.Length > 0)
                {
                    szData = szData.Substring(0, 1);
                }

                dataRecibida += szData;

                if (szData.Equals(cadenaTerminador))
                {
                    OnRecepcionData(dataRecibida);
                    dataRecibida = string.Empty;
                }

                WaitForData();
            }
            catch (ObjectDisposedException)
            {
                OnSucesosCliente("Se finalizo la conexion con el servidor");//"OnDataReceived: Socket has been closed");
            }
            catch (SocketException se)
            {
                if ((se.SocketErrorCode == SocketError.Disconnecting) ||
                     (se.SocketErrorCode == SocketError.ConnectionReset))
                {
                    OnTerminoConexion("Se cerro la conexion con el servidor");//se.Message);
                }
                else
                {
                    OnSucesosCliente(se.Message);
                }
            }
        }
        #endregion
    }
}
